package com.wh1200.mybatis.xmlless.model

import com.wh1200.mybatis.xmlless.AnnotatedElementDetailInfo
import com.wh1200.mybatis.xmlless.annotations.DeleteValue
import com.wh1200.mybatis.xmlless.enums.JoinType
import com.wh1200.mybatis.xmlless.enums.TestType
import com.wh1200.mybatis.xmlless.model.component.JoinDeclaration
import com.wh1200.mybatis.xmlless.model.component.JoinOnConditionDeclaration
import com.wh1200.mybatis.xmlless.model.component.TestConditionDeclaration
import com.wh1200.mybatis.xmlless.model.component.TestConditionGroup
import com.wh1200.mybatis.xmlless.model.exception.BuildSQLException
import com.wh1200.mybatis.xmlless.model.exception.XmlLessException
import com.wh1200.mybatis.xmlless.resolver.MappingResolver
import com.wh1200.mybatis.xmlless.util.ColumnUtils
import com.wh1200.mybatis.xmlless.util.ScriptUtils
import net.sf.jsqlparser.parser.CCJSqlParserUtil
import net.sf.jsqlparser.schema.Column


/**
 *
 * @author 吴昊
 * @since 0.0.1
 */
data class FieldMappingList(
    val mappings: List<FieldMapping>,
    val tableInfo: XmllessTableInfo,
    val modelClassName: String,
) {

  /** table name */
  val tableName = tableInfo.tableName

  /** 自动生成主键的字段 */
  private val autoGeneratedKeyField = this.mappings.find { it.isKeyField && it.isAutoGeneratedKey }

  /**
   * 获取插入的列名称语句, join属性自动被过滤
   * 例如： t_student.id, t_student.name
   */
  fun insertFields(selectedProperties: Properties, batch: Boolean): List<String> {
    return getInsertFields(selectedProperties, batch).map {
      val col = ColumnUtils.wrapColumn(it.column) + ","
      if (autoGeneratedKeyField == null || autoGeneratedKeyField.property != it.property) {
        col
      } else {
        wrapColIfIdIsNullOrEmpty(col)
      }
    }
  }

  /**
   * 获取插入的对象属性语句，例如：
   * #{name}, #{age}, #{details,typeHandler=com.wh1200.project.mybatis.handler.DetailsHandler}
   */
  fun insertProperties(
      prefix: String? = null, insertProperties: Properties = Properties(),
      batch: Boolean = false
  ): List<String> {
    return getInsertFields(insertProperties, batch).map {
      if (it == autoGeneratedKeyField) {
        wrapColIfIdIsNullOrEmpty(it.getInsertPropertyExpression(prefix) + ",")
      } else {
        it.getInsertPropertyExpression(prefix) + ","
      }
    }
  }

  /**
   * select查询中的join语句
   */
  fun selectJoins(
      level: Int,
      includedTableAlias: List<String> = listOf(),
      joinTableName: TableName? = null,
      onlyIncludesTables: List<TableName>? = null,
      deepJoin: Boolean = true
  ): List<JoinDeclaration> {
    if (level > 3) {
      return listOf()
    }
    val joinInfoList = mappings.filter {
      it.joinInfo != null && it.joinInfo.joinTable.alias.prefixWith(joinTableName?.alias) in includedTableAlias
    }.mapNotNull { it.joinInfo }
        .distinctBy { it.joinTable.alias }
        .filter {
          onlyIncludesTables == null || onlyIncludesTables.map { tableName -> tableName.name }
              .contains(it.joinTable.name)
        }
    val joins = mutableListOf<JoinDeclaration>()
    joinInfoList.forEach { joinInfo ->
      buildJoinDeclaration(joinInfo, joins, joinTableName, deepJoin, level, includedTableAlias)
    }
    joins.addAll(resolveJoinFromForeignProperty(includedTableAlias, joinTableName, level))
    return joins.distinctBy { it.joinTable.getAliasOrName() }
  }

  fun getLogicDelFlagValue(logicalType: DeleteValue): Any? {
    val logicDelMapping = this.mappings.find { it.tableFieldInfo.isLogicDelete }
    return if (logicDelMapping != null) {
      when (logicalType) {
        DeleteValue.Deleted -> logicDelMapping.tableFieldInfo.logicDeleteValue
        else                -> logicDelMapping.tableFieldInfo.logicNotDeleteValue
      }
    } else {
      when (logicalType) {
        DeleteValue.Deleted -> true
        else                -> false
      }
    }
  }

  private fun buildJoinDeclaration(
      joinInfo: JoinInfo,
      joins: MutableList<JoinDeclaration>,
      joinTableName: TableName?,
      deepJoin: Boolean,
      level: Int,
      includedTableAlias: List<String>
  ) {
    val joinPropertyName = joinInfo.getJoinProperty(tableInfo)
    val targetJoinTable = TableName(joinInfo.joinTable.name, joinInfo.joinTable.alias.prefixWith(joinTableName?.alias))
    when (joinInfo) {
      is ObjectJoinInfo,
      is PropertyJoinInfo -> {
        val joinProperty = mappings.firstOrNull { it.property == joinPropertyName }
          ?: MappingResolver.getMappingCache(modelClassName)?.mappings?.find {
            it.property == joinPropertyName
          } ?: throw BuildSQLException("无法解析join属性$joinPropertyName")
        val declaration = JoinDeclaration(
            joinInfo.type,
            targetJoinTable,
            JoinOnConditionDeclaration(
                tableInfo,
                joinTableName ?: TableName(tableName),
                joinInfo.joinTableInfo,
                Column(joinPropertyName),
                targetJoinTable,
                CCJSqlParserUtil.parseExpression(joinInfo.targetExpression.toString()),
                joinProperty.isJsonArray
            )
        )
        val logicFieldInfo = joinInfo.joinTableInfo?.logicDeleteFieldInfo
        if (joinInfo is ObjectJoinInfo && logicFieldInfo != null) {
          val specificValue = logicFieldInfo.logicNotDeleteValue
          declaration.logicDeleteConditionSql = JoinOnLogicDeleteCondition(
              joinInfo.joinTable.alias + "." + logicFieldInfo.column,
              if (specificValue is String) {
                SpecificValue(
                    stringValue = specificValue,
                    nonStringValue = ""
                )
              } else {
                SpecificValue(
                    stringValue = "",
                    nonStringValue = specificValue.toString()
                )
              }
          )
        }
        joins.add(declaration)
      }
    }
    if (deepJoin && joinInfo is ObjectJoinInfo) {
      joins.addAll(joinInfo.selectDeepJoins(level, includedTableAlias, targetJoinTable))
    }
  }

  private fun getInsertFields(selectedProperties: Properties, batch: Boolean): List<FieldMapping> {
    return mappings.filter {
      !it.insertIgnore
    }.filter {
      if (selectedProperties.isIncludeNotEmpty()) {
        it.property in selectedProperties
      } else {
        it.property !in selectedProperties.excludes
      }
    }.filter {
      !batch || it != autoGeneratedKeyField
    }
  }

  private fun wrapColIfIdIsNullOrEmpty(exp: String): String {
    if (autoGeneratedKeyField == null) {
      return exp
    }
    val tests = getTestsConditionOfField(autoGeneratedKeyField)
    return if (tests.hasCondition()) {
      ScriptUtils.convertIf(exp, tests.toSql(), true)
    } else {
      exp
    }
  }

  private fun resolveJoinFromForeignProperty(
      includedTableAlias: List<String>,
      joinTableName: TableName?,
      level: Int
  ): List<JoinDeclaration> {
    val result = mutableListOf<JoinDeclaration>()
    mappings.forEach { mapping ->
      mapping.joinObjects.forEach { joinObjectInfo ->
        val originJoinTableName = joinObjectInfo.getTableName()
        val newTableName = TableName(originJoinTableName
            .name, originJoinTableName.alias.prefixWith(joinTableName?.alias))
        if (newTableName.alias in includedTableAlias || originJoinTableName.alias in includedTableAlias) {
          val targetTableInfo = joinObjectInfo.getJoinTableInfo()
          val thisJoinValueExpression = if (joinObjectInfo.joinOnThisProperty.isBlank()) {
            CCJSqlParserUtil.parseExpression(mapping.property)
          } else {
            CCJSqlParserUtil.parseExpression(joinObjectInfo.joinOnThisProperty)
          }
          val targetField: AnnotatedElementDetailInfo?
          val targetExpression = if (joinObjectInfo.joinOnTargetProperty.isBlank()) {
            if (targetTableInfo.keyProperty.isNullOrBlank()) {
              throw XmlLessException("持久化对象${targetTableInfo.entityTypeName}缺少主键字段")
            }
            targetField = targetTableInfo.classFields.find { it.name == targetTableInfo.keyProperty }
            CCJSqlParserUtil.parseExpression(targetTableInfo.keyProperty)
          } else {
            targetField = targetTableInfo.classFields.find { it.name == joinObjectInfo.joinOnTargetProperty }
            CCJSqlParserUtil.parseExpression(joinObjectInfo.joinOnTargetProperty)
          }
          val declaration = JoinDeclaration(
              JoinType.LEFT,
              newTableName,
              JoinOnConditionDeclaration(
                  tableInfo,
                  joinTableName ?: TableName(tableName),
                  targetTableInfo,
                  thisJoinValueExpression,
                  newTableName,
                  targetExpression,
                  isTargetJson = targetField?.isJsonMappingProperty ?: false
              )
          )
          val logicFieldInfo = targetTableInfo.logicDeleteFieldInfo
          if (logicFieldInfo != null) {
            val specificValue = logicFieldInfo.logicNotDeleteValue
            declaration.logicDeleteConditionSql = JoinOnLogicDeleteCondition(
                declaration.joinTable.alias + "." + logicFieldInfo.column,
                if (specificValue is String) {
                  SpecificValue(stringValue = specificValue)
                } else {
                  SpecificValue(nonStringValue = specificValue.toString())
                }
            )
          }
          result.add(declaration)
          val mappings = MappingResolver.getMappingCache(joinObjectInfo.getJoinTableInfo().entityTypeName)
          if (mappings != null) {
            val subJoins = mappings.selectJoins(
                level + 1,
                includedTableAlias,
                newTableName
            )
            result.addAll(subJoins)
          }
        }
      }
    }
    return result
  }

  private fun getTestsConditionOfField(autoGeneratedKeyField: FieldMapping): TestConditionGroup {
    val tests = TestConditionGroup()
    if (autoGeneratedKeyField.isNumberIdType) {
      tests.add(
          TestConditionDeclaration(
              autoGeneratedKeyField.property,
              TestType.NotNull
          )
      )
      tests.add(
          TestConditionDeclaration(
              autoGeneratedKeyField.property,
              TestType.GteZero
          )
      )
    } else if (autoGeneratedKeyField.isStringType) {
      tests.add(
          TestConditionDeclaration(
              autoGeneratedKeyField.property,
              TestType.NotNull
          )
      )
      tests.add(
          TestConditionDeclaration(
              autoGeneratedKeyField.property,
              TestType.NotBlank
          )
      )
    }
    return tests
  }

}

internal fun String.prefixWith(prefix: String?): String {
  if (prefix.isNullOrBlank()) {
    return this
  }
  return prefix + "_" + this
}
